package com.ccb.sc.auth;

import com.ccb.sc.constant.AppConst;
import com.ccb.sc.login.service.IAppTokenService;
//psl import com.ccb.sc.common.bind.exception.BizException;
import com.ccb.sc.common.code.BizCodeFace;
import com.ccb.sc.common.code.ErrorCode;
import com.ccb.sc.common.constant.CommonConstant;
import com.ccb.sc.common.modules.common.service.IRedisService;
import com.auth0.jwt.interfaces.Claim;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author ken
 * @version 1.0
 * @date 2020-09-14
 */
@Slf4j
public class TokenAuthInterceptor implements HandlerInterceptor {

    private IAppTokenService tokenService;
    private IRedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        // 增加ip黑名单限制
        /*String ip = IpUtil.getIpAddress(request);
        Object backListLimitObj = this.redisService.get(String.format(CommonRedisKey.BACKLIST_IP_LIMIT, ip));
        if (backListLimitObj != null && (int)backListLimitObj > 3) {
            this.redisService.set(String.format(CommonRedisKey.BACKLIST_IP, ip), true);
            log.error("ip黑名单拦截:[{}],url:[{}],params[{}]", ip, request.getRequestURI(), JSON.toJSONString(request.getParameterMap()));
            return false;
        }

        Object backListObj = this.redisService.get(String.format(CommonRedisKey.BACKLIST_IP, ip));
        if (backListObj != null) {
            log.error("ip黑名单拦截:[{}],url:[{}],params[{}]", ip, request.getRequestURI(), JSON.toJSONString(request.getParameterMap()));
            return false;
        }

        String luaScript = buildLuaScript();
        RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);

        ImmutableList<String> okeys = ImmutableList.of(String.format(CommonRedisKey.GLOBAL_LIMIT_REQUEST_ONE, ip));
        Number mCount = this.redisService.execute(redisScript, okeys, 120, 60);

        ImmutableList<String> fkeys = ImmutableList.of(String.format(CommonRedisKey.GLOBAL_LIMIT_REQUEST_FIVE, ip));
        Number fCount = this.redisService.execute(redisScript, fkeys, 500, 300);

        ImmutableList<String> dkeys = ImmutableList.of(String.format(CommonRedisKey.GLOBAL_LIMIT_REQUEST_DAY, ip));
        Number dCount = this.redisService.execute(redisScript, dkeys, 100*60*24, 60*60*24);

        boolean isLimit = false;
        if (mCount != null && mCount.intValue() > 120) {
            log.error("触发分钟限流，次数 [{}]，ip = [{}]", mCount, ip);
            this.redisService.delete(String.format(CommonRedisKey.GLOBAL_LIMIT_REQUEST_ONE, ip));
            this.redisService.getNextByDayClean(String.format(CommonRedisKey.BACKLIST_IP_LIMIT, ip));
            isLimit = true;
        }
        if (fCount != null && fCount.intValue() > 500) {
            log.error("触发5分钟限流，次数 [{}]，ip = [{}]", fCount, ip);
            this.redisService.delete(String.format(CommonRedisKey.GLOBAL_LIMIT_REQUEST_FIVE, ip));
            this.redisService.getNextByDayClean(String.format(CommonRedisKey.BACKLIST_IP_LIMIT, ip));
            isLimit = true;
        }
        if (dCount != null && dCount.intValue() > 10*60*24) {
            log.error("触发天级限流，次数 [{}]，ip = [{}]", dCount, ip);
            this.redisService.delete(String.format(CommonRedisKey.GLOBAL_LIMIT_REQUEST_DAY, ip));
            this.redisService.getNextByDayClean(String.format(CommonRedisKey.BACKLIST_IP_LIMIT, ip));
            isLimit = true;
        }*/

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        TokenAuth auth = method.getAnnotation(TokenAuth.class);
        if (auth == null) {
            auth = method.getDeclaringClass().getAnnotation(TokenAuth.class);
        }
        /*if (auth == null) {
            return true;
        }*/
        // 获取token
        Token token = null;
        String tokenStr = TokenUtil.getToken(request);
        if (StringUtils.hasText(tokenStr)) {
            Map<String, Claim> claims = JWTHelper.unSign(tokenStr);
            if (claims != null && claims.containsKey(AppConst.MEMBER_SESSION_ATTR_KEY)) {
                Claim sessionKey = claims.get(AppConst.MEMBER_SESSION_ATTR_KEY);
                if (sessionKey != null) {
                    token = this.tokenService.getTokenModel(sessionKey.asString());
                    if (token != null && tokenStr.equals(token.getSign())) {
                        /*if (isLimit) {
                            this.tokenService.deleteToken(sessionKey.asString());
                            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.PERMISSION_DENIED).message("您还未登录，请先登录！"));
                        }*/

                        TokenModel model = new TokenModel(token);
                        request.setAttribute(CommonConstant.CURRENT_USER, model);
                        this.tokenService.delayToken(sessionKey.asString());
                    }
                }
            }
        }
        if (auth != null && auth.require() && (token == null || !tokenStr.equals(token.getSign()))) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.PERMISSION_DENIED).message("您还未登录，请先登录！"));
        }
        return true;
    }

    @Autowired
    @Lazy
    public void setTokenService(IAppTokenService tokenService) {
        this.tokenService = tokenService;
    }

    @Autowired
    public void setRedisService(IRedisService redisService) {
        this.redisService = redisService;
    }


    /**
     * 限流 脚本
     *
     * @return lua脚本
     */
    public String buildLuaScript() {
        StringBuilder lua = new StringBuilder();
        lua.append("local c");
        lua.append("\nc = redis.call('get',KEYS[1])");
        // 调用不超过最大值，则直接返回
        lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
        lua.append("\nreturn c;");
        lua.append("\nend");
        // 执行计算器自加
        lua.append("\nc = redis.call('incr',KEYS[1])");
        lua.append("\nif tonumber(c) == 1 then");
        // 从第一次调用开始限流，设置对应键值的过期
        lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
        lua.append("\nend");
        lua.append("\nreturn c;");
        return lua.toString();
    }

}

